package fybug.nulll.pdstream.IO.Stream;
import com.sun.istack.internal.NotNull;
import com.sun.istack.internal.Nullable;

import java.io.IOException;
import java.io.OutputStream;

import fybug.nulll.pdfunctionlibrary.Processing.Check;
import fybug.nulll.pdfunctionlibrary.Processing.Err.Stop;
import fybug.nulll.pdfunctionlibrary.Util.Processing.Arrarys;
import fybug.nulll.pdstream.IO.Error.ByteStreamErr;
import fybug.nulll.pdstream.IO.I_O;
import fybug.nulll.pdstream.Interface.UpdateSize;
import fybug.nulll.pdstream.Source.StreamSource;
import fybug.nulll.pdstream.StreamOperator;
/**
 * <h2>字节流输出器.</h2>
 * <pre>
 * <b>建议使用工场类 {@link I_O} 构造</b>
 *
 * 从绑定的 <b>操作源</b> 中获取的被操作流和绑定的数据进行写入
 * 支持 <b>单个数据写入多个流</b>
 * 可传入 <b>不绑定在源中</b> 的要写入的数据
 * 可使用该操作器中的方法对操作源进行 <b>流和数据的重绑定</b>
 * 可以使用 <b>连续操作</b> ,有效解决不稳定流的输出问题</pre>
 * <pre>常规操作方法：
 * {@link #output()}
 * {@link #output(byte[])}
 * </pre>
 *
 * @author fybug
 * @version 0.0.3
 * @see StreamOperator
 * @see OutputStream
 * @see I_O
 * @see ByteStreamErr
 * @since JDK 1.8
 */
@SuppressWarnings( "all" )
public final
class Output extends StreamOperator<OutputStream, byte[]> {
    /** <p>构造一个操作器并绑定操作源.</p> */
    public
    Output(@Nullable final StreamSource<OutputStream, byte[]> r) { binSource(r); }

    /*
     * MainSource
     */

    @Override
    @NotNull
    public final
    Output binSource(@Nullable final StreamSource<OutputStream, byte[]> r)
    {
        super.binSource(r);
        return this;
    }

    /** <p>重绑定操作源中的流.</p> */
    @NotNull
    public final
    Output binOutputStream(@Nullable final OutputStream... w) {
        try {
            source().binStream(w);
        } catch ( Stop ignored ) {
        }
        return this;
    }

    /** <p>增加操作源中的流.</p> */
    @NotNull
    public final
    Output appendOutputStream(@NotNull final OutputStream... o) {
        try {
            @NotNull StreamSource<OutputStream, byte[]> source = source();
            OutputStream[] outputStreams;
            try {
                outputStreams = source.stream();
            } catch ( Stop e ) {
                outputStreams = null;
            }
            source.binStream(Arrarys.append(outputStreams, o));
        } catch ( Stop ignored ) {
        }
        return this;
    }

    /** <p>重绑定操作源中的数据.</p> */
    @NotNull
    public final
    Output binData(@Nullable final byte[] bytes) {
        try {
            source().binOutPut(bytes);
        } catch ( Stop ignored ) {
        }
        return this;
    }

    /*
     * Write
     */

    /**
     * <p>将绑定的数据输出到绑定的流中.</p>
     * <pre>
     * 获取源中绑定的数据并输出到所有流中
     * 我也不知道适用于什么流 <b>(>^ω^<)喵~</b>
     * </pre>
     *
     * @throws ByteStreamErr 输出过程发生错误
     */
    @NotNull
    public final
    Output output() throws ByteStreamErr {
        try {
            output(source().getOutPut());
        } catch ( Stop ignored ) {
        }
        return this;
    }

    /**
     * <p>将数据输出到绑定的流中.</p>
     * <pre>
     * 将传入的数据输出到所有流中
     * 我也不知道适用于什么流 <b>(>^ω^<)喵~</b>
     * </pre>
     *
     * @throws ByteStreamErr 输出过程发生错误
     */
    @NotNull
    public final
    Output output(@Nullable final byte[] bytes) throws ByteStreamErr {
        @NotNull final OutputStream[] outputStreams;
        // 检查
        try {
            outputStreams = source().stream();
            Check.checkNull(bytes);
        } catch ( Stop ignored ) {
            return this;
        }
        @NotNull OutputStream stream = null;
        try {
            synchronized ( outputStreams ){
                for ( int i = 0; i < outputStreams.length; i++ ){
                    /* 批量写入 */
                    stream = outputStreams[i]; // 减少寻址
                    // 写入
                    stream.write(bytes);
                    // 刷新
                    stream.flush();
                }
            }
        } catch ( IOException e ) {
            throw new ByteStreamErr(e.getMessage(), null, stream);
        }
        return this;
    }

    /**
     * <p>使用连续操作将绑定的数据写入到绑定的流中.</p>
     * <pre>
     * 每次写入一个片段
     * 每个片段都会写入所有流中
     * 适用于所有流
     * </pre>
     *
     * @param size 片段大小
     *
     * @throws ByteStreamErr 输出过程出错
     */
    @NotNull
    public final
    Output sustained(final int size) throws ByteStreamErr
    { return monitor(size, checkUpSize()); }

    /**
     * <p>使用连续操作将绑定的数据写入到绑定的流中.</p>
     * <pre>
     * 每次写入一个片段
     * 每个片段都会写入所有流中
     * 可以使用接口 <b>动态调整片段</b> 大小
     * 在写入过程中设置片段大小为{@code 0} 可以中断操作
     * 适用于所有流
     * </pre>
     *
     * @param size       初始片段大小
     * @param updateSize 监听接口
     *
     * @throws ByteStreamErr 输出过程出错
     * @see UpdateSize
     */
    @NotNull
    public final
    Output monitor(int size, @Nullable final UpdateSize updateSize) throws ByteStreamErr {
        @NotNull final OutputStream[] s;
        @NotNull byte[] b;
        // 检查
        try {
            s = source().stream();
            b = source().getOutPut();
            // 参数错误
            if (size <= 0)
                return this;
        } catch ( Stop e ) {
            return this;
        }
        @NotNull OutputStream outputStream = null;
        try {
            synchronized ( s ){
                if (updateSize != null)
                    updateSize.init();
                for ( int i = 0; i < b.length; i += size ){
                    for ( int i1 = 0; i1 < s.length; i1++ ){
                        outputStream = s[i1];
                        // 根据缓存区大小将片段写入
                        outputStream.write(b, i, b.length - i > size ? size : b.length - i);
                    }
                    // 刷新缓冲区大小
                    if (updateSize != null)
                        size = updateSize.updataSize(size);
                }
                b = null;
                for ( int i = 0; i < s.length; i++ ){
                    // 刷新流
                    outputStream = s[i];
                    outputStream.flush();
                }
            }
            outputStream = null;
        } catch ( Stop ignored ) {
        } catch ( IOException e ) {
            throw new ByteStreamErr(e.getMessage(), null, outputStream);
        } finally {
            if (updateSize != null)
                updateSize.destroy();
        }
        return this;
    }
}
